#include "client.h"
#include "../tools/charlist.h"
#include "socketfilepair.h"

ClientReturn create_Client(Client **client, const char *host, const char *port, const char *nom)
{
  if(client == NULL)
    return CLIENT_ERROR;
  if((*client) != NULL)
    return CLIENT_ERROR;
  (*client) = (Client *) malloc (sizeof(Client));
  if((*client == NULL))
    return CLIENT_MALLOC_ERROR;
  return initialize_Client((*client), host, port, nom);
}

void delete_Client(Client **client)
{
  if(client == NULL)
    return;
  if((*client) == NULL)
    return;
  testament_Client((*client));
  free((*client));
  (*client) = NULL;
}

void createCopy_Client(Client **clientA, const Client *clientB)
{
  if(clientB != NULL)
    {
      ClientReturn cRet;
      (*clientA) = NULL;
      cRet = create_Client(clientA, clientB->host, clientB->port, clientB->nom);
      if(cRet != CLIENT_SUCCESS)
	return;
      (*clientA)->socket = clientB->socket;
      (*clientA)->haveLFile = clientB->haveLFile;
      testament_List(&((*clientA)->lFile));
      testament_List(&((*clientA)->lFileUse));
      testament_List(&((*clientA)->lSocketFileDownload));
      initializeCopy_List(&((*clientA)->lFile), &(clientB->lFile));
      initializeCopy_List(&((*clientA)->lFileUse), &(clientB->lFileUse));
      initializeCopy_List(&((*clientA)->lSocketFileDownload), &(clientB->lSocketFileDownload));
    }
  else
    clientA = NULL;
}
  
ClientReturn initialize_Client(Client *client, const char *host, const char *port, const char *nom)
{
  if(client == NULL)
    return CLIENT_ERROR;
  if(host == NULL)
    client->host = NULL;
  else 
    {
      client->host = (char *) malloc ((strlen(host) + 1) * sizeof(char));
      if(client->host == NULL)
	return CLIENT_MALLOC_ERROR;
      strcpy(client->host, host);
    }
  if(port == NULL)
    client->port = NULL;
  else
    {
      client->port = (char *) malloc ((strlen(port) + 1) * sizeof(char));
      if(client->port == NULL)
	{
	  testament_Client(client);
	  return CLIENT_MALLOC_ERROR;
	}
      strcpy(client->port, port);
    }
  if(nom == NULL)
    client->nom = NULL;
  else
    {
      client->nom = (char *) malloc ((strlen(nom) + 1) * sizeof(char));
      if(client->nom == NULL)
	{
	  testament_Client(client);
	  return CLIENT_MALLOC_ERROR;
	}
      strcpy(client->nom, nom);
    }
  client->socketPair = NULL;
  client->file = NULL;
  client->fileDest = NULL;
  client->socket = -1;
  initialize_List(&(client->lFile), create_CharList, delete_CharList, compare_CharList, print_CharList);
  initialize_List(&(client->lFileUse), create_CharList, delete_CharList, compare_CharList, print_CharList);
  initialize_List(&(client->lSocketFileDownload), create_SocketFilePair, delete_SocketFilePair, compare_SocketFilePair, NULL);
  client->haveLFile = 0;
  return CLIENT_SUCCESS;
}

void testament_Client(Client *client)
{
  if(client == NULL)
    return;
  if(client->host != NULL)
    {
      free(client->host);
      client->host = NULL;
    }
  if(client->port != NULL)
    {
      free(client->port);
      client->port = NULL;
    }
  if(client->socketPair != NULL)
    {
      close(client->socketPair[0]);
      close(client->socketPair[1]);
      client->socketPair = NULL;
    }
  if(client->file != NULL)
    {
      free(client->file);
      client->file = NULL;
    }
  if(client->fileDest != NULL)
    {
      free(client->fileDest);
      client->fileDest = NULL;
    }
 if(client->nom != NULL)
    {
      free(client->nom);
      client->nom = NULL;
    }
  client->socket = -1;

  testament_List(&(client->lFile));
  testament_List(&(client->lFileUse));
  testament_List(&(client->lSocketFileDownload));
  client->haveLFile = 0;
}

int connexion_Client(Client *client)
{
  struct addrinfo hints;
  struct addrinfo * result, * rp;
  int res, bon, s;

  memset(&hints, 0, sizeof(struct addrinfo));
  hints.ai_family = AF_INET;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags = AI_PASSIVE;

  res = getaddrinfo(client->host, client->port, &hints, &result);
  if(res != 0)
    {
      fprintf(stderr, "getaddinfo:%s\n",gai_strerror(res));
      return -1;
    }

  bon = 0;
  for( rp = result; rp != NULL; rp = rp->ai_next)
    {
      s = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);
      if( s == -1) 
	{
	  fprintf(stderr, "Création de la socket impossible\n");
	  continue;
	}
      res = connect(s , rp->ai_addr, rp->ai_addrlen);
      if (res == 0)
	{
	  bon = 1;
	  break;
	}
      fprintf(stderr, "Impossible de réserver l'adresse\n");
      close(s);
    }
  freeaddrinfo(result);
  if (bon == 0)
    {
      fprintf(stderr, "Impossible d'obtenir une adresse\n");
      return -1;
    }
  client->socket = s;
  return s;
}

ClientReturn waitHello_Client(Client *client)
{
  char *message = NULL;

  if(receive_ToolsCS(&message, client->socket) != TOOLSCSRETURN_SUCCESS)
    {
      free(message);
      return CLIENT_RECV_ERROR;
    }
  if(strncmp(message, "HEL0", 4) == 0)
    {
      free(message);
      return CLIENT_SUCCESS;
    }
  free(message);
  return CLIENT_ERROR;
}

ClientReturn waitOleh_Client(Client *client)
{
  char *message = NULL;

  if(receive_ToolsCS(&message, client->socket) != TOOLSCSRETURN_SUCCESS)
    {
      free(message);
      return CLIENT_RECV_ERROR;
    }
  if(strncmp(message, "OLEH", 4) == 0)
    {
      free(message);
      return CLIENT_SUCCESS;
    }
  free(message);
  return CLIENT_ERROR;
}

ClientReturn sendCommand_Client(const int socket, const char *cmd)
{
  char *cmdRet = NULL;
  char *cmdCmp = NULL;
  ClientReturn cRet = CLIENT_SUCCESS;
  if(send_ToolsCS(socket, cmd) != TOOLSCSRETURN_SUCCESS)
    {
      return CLIENT_SEND_ERROR;
    }
  cmdCmp = (char *) malloc ((strlen(cmd) + 6) * sizeof(char));
  if(receive_ToolsCS(&cmdRet, socket) != TOOLSCSRETURN_SUCCESS)
    {
      cRet = CLIENT_RECV_ERROR;
    }
  free(cmdRet);
  free(cmdCmp);
  return cRet;
}

ClientReturn getListFile_Client(Client *client)
{
  char *message = NULL;
  char continu = 1;
  int cRet;
  clear_List(&(client->lFile));
  client->haveLFile = 1;
  if((cRet = sendCommand_Client(client->socket, "LIST\n")) != CLIENT_SUCCESS)
    {
      return cRet;
    }
  do
    {
      if(receive_ToolsCS(&message, client->socket) != TOOLSCSRETURN_SUCCESS)
	{
	  if(message)
	    free(message);
	  return CLIENT_RECV_ERROR;
	}
      if(strstr(message, "FAIL") != NULL)
	{
	  free(message);
	  return CLIENT_SERVER_FAIL;
	}
      if(strcmp(message, "ENDL") != 0)
	{
      	  message += 5;
	  message[strlen(message)] = '\0';
	  insertLast_List(&(client->lFile), message);
	  message -= 5;
	}
      else
	continu = 0;
      if(message != NULL)
	{
	  free(message);
	  message = NULL;
	}
    }while(continu);
  return CLIENT_SUCCESS;
}

void *threadGetFile_Client(void *arg0)
{
  char *message = NULL;
  char *endptr = NULL;
  char buff[1024];
  int size = 0;
  int sizeReception = 0;
  int fd;
  int ret;
  int *sockPair;
  double prev = 0;
  char *file;
  char *fileDest;
  Client *client = (Client *) arg0;
  sockPair = client->socketPair;
  file = client->file;
  fileDest = client->fileDest;
  message = (char *) malloc ((strlen(file) + 7) * sizeof(char));
  if(message == NULL)
    {
      /*
      return CLIENT_MALLOC_ERROR;
      */
      quit_Client(client);
      delete_Client(&client);
      return NULL;
    }
  sprintf(message, "GETF %s\n", file);

  if((ret = sendCommand_Client(client->socket, message)) != CLIENT_SUCCESS)
    {
      free(message);
      /*
      return ret;
      */
      quit_Client(client);
      delete_Client(&client);
      return NULL;
    }
  free(message);
  message = NULL;

  if(receive_ToolsCS(&message, client->socket) != TOOLSCSRETURN_SUCCESS)
    {
      free(message);
      /*
      return CLIENT_RECV_ERROR;
      */
      quit_Client(client);
      delete_Client(&client);
      return NULL;
    }
  if(strstr(message, "SIZE") == NULL)
    {
      /*      
      ClientReturn cRet = CLIENT_ERROR;
      
      if(strstr(message, "FAIL") != NULL)
	{
	  cRet = CLIENT_SERVER_FAIL;
	}
      else if(strstr(message, "BUSY") != NULL)
	{
	  cRet = CLIENT_FILE_BUSY;
	}
      else if(strstr(message, "NONE") != NULL)
	{
	  cRet = CLIENT_FILE_UNKNOW;
	}
      */
      free(message);
      quit_Client(client);
      delete_Client(&client);
      return NULL;
      /*
      return cRet;
      */
    }
  
  if(strlen(message) <= 5)
    {
      free(message);
      /*
      return CLIENT_ERROR;
      */
      quit_Client(client);
      delete_Client(&client);
      return NULL;
    }
  message += 5;
  size = strtol(message, &endptr, 10);

  if(endptr == message)
    {
      free(message);
      /*
      return CLIENT_ERROR;
      */
      quit_Client(client);
      delete_Client(&client);
      return NULL;
    }
  message -= 5;
  free(message);
  insertLast_List(&(client->lFileUse), (Element)file);
  fd = open(fileDest, O_WRONLY|O_CREAT|O_TRUNC,
	     S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
  if(fd < 0)
    {
      
      if(sockPair)
	{
	  int g = 0;
	  send(sockPair[1], &g, sizeof(int), 0);
	}
      send(client->socket, "STOP\n", 4*sizeof(char), 0);
      /* Vide la socket (normalement un morceau de fichier) */
      while(recv(client->socket, &buff, 1023 * sizeof(char), MSG_DONTWAIT) > 0);
      quit_Client(client);
      delete_Client(&client);
      return NULL;
      /*
      return CLIENT_FILE_NULL;
      */
    }
  while(sizeReception < size)
    {
      struct timeval tvs;
      struct timeval tve;
      /*
      char *speed;
      */

      gettimeofday(&tvs, NULL);
      ret = recv(client->socket, &buff, ((size - sizeReception) < 1023 ? (size - sizeReception) : 1023) * sizeof(char), 0);
      gettimeofday(&tve, NULL);
      sizeReception += ret;
      if(sockPair != NULL)
	{
	  /*
	  char pourcent[25];
      	  speed = calc_speed(ret, tve.tv_usec - tvs.tv_usec, 1);

	  printf("\r%s", speed);
	  fflush(stdout);
	  
	  sprintf(pourcent, "%3d%% S%d\n", (int)100.0 * sizeReception / size, strlen(speed));
	  
	  send(sockPair[0], pourcent, strlen(pourcent)*sizeof(char), 0);
	  send(sockPair[0], speed, strlen(speed) * sizeof(char), 0);
	  */
	  double g = (double)100.0 * sizeReception / size;
	  if((int)prev < (int)g)
	    {
	      send(sockPair[1], &g, sizeof(double), 0);
	      prev = g;
	    }
	}

      if(ret == -1)
	{
	  close(fd);
	  /*
	  return CLIENT_RECV_ERROR;
	  */
	  quit_Client(client);
	  delete_Client(&client);
	  return NULL;
	}
      buff[ret + 1] = '\0';

      if(write(fd, buff, ret * sizeof(char)) == -1)
	perror("");
    }
  if(close(fd) == -1)
    {
      /*
      return CLIENT_ERROR;
      */
      quit_Client(client);
      delete_Client(&client);
      return NULL;
    }
  quit_Client(client);
  delete_Client(&client);
  return NULL;
}

ClientReturn getFile_Client(Client *client, const char *file, const char *fileDest, int *sockPair)
{
  Client *clientDownload = NULL;
  pthread_t threadDownload;
  SocketFilePair socketFilePair;
  pthread_attr_t thread_attr;

  if(file == NULL)
    return CLIENT_FILE_NULL;
  if(fileDest == NULL)
    return CLIENT_FILE_NULL;

  /* 
     Lorsque que le client essaie de télécharger un fichier, celui-ci va être télécharger dans un nouveau socket pour éviter de bloquer le client.
     pour cela, il faut refaire un connexion puis faire le getf sur la socket en question... bien entendu, il faut réalisé un thread. */
  create_Client(&clientDownload, client->host, client->port, client->nom);

  clientDownload->socketPair = sockPair;
  clientDownload->file = (char *) malloc ((strlen(file) + 1) * sizeof(char));
  strcpy(clientDownload->file, file);
  clientDownload->fileDest = (char *) malloc ((strlen(fileDest) + 1) * sizeof(char));
  strcpy(clientDownload->fileDest, fileDest);
  if(connexion_Client(clientDownload) == -1)
    {
      testament_Client(clientDownload);
      return CLIENT_SERVER_FAIL;
    }

  waitHello_Client(clientDownload);
  initialize_SocketFilePair(&socketFilePair, file, clientDownload->socket, sockPair);
  insertLast_List(&(client->lSocketFileDownload), &socketFilePair);

  if(pthread_attr_init(&thread_attr) != 0)
    {
      fprintf (stderr, "[getFile_Client]Erreur pthread_attr_init\n");
      return CLIENT_ERROR;
    }

  if(pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED) != 0) 
    {
      fprintf (stderr, "[getFile_Client]Erreur pthread_attr_setdetachstate\n");
      return CLIENT_ERROR;
    }
  if(pthread_create(&threadDownload, &thread_attr, threadGetFile_Client, clientDownload) != 0)
    {
      testament_SocketFilePair(&socketFilePair);
      delete_Client(&clientDownload);
      return CLIENT_ERROR;
    }
  testament_SocketFilePair(&socketFilePair);
  return CLIENT_SUCCESS;
}

char *calc_speed(int bytes, double msecs, int pad)
{
  static char res[20];
  static const char *rate_names[] = {"B/s", "KB/s", "MB/s", "GB/s" };
  int units = 0;
  double dlrate;
  if(msecs < 0)
    return NULL;
  if(bytes < 0)
    return NULL;

  if (msecs == 0)
    /* If elapsed time is exactly zero, it means we're under the
       resolution of the timer.  This can easily happen on systems
       that use time() for the timer.  Since the interval lies between
       0 and the timer's resolution, assume half the resolution.  */
    msecs = 10 / 2.0;

  dlrate = 1000.0 * bytes / msecs;
  if (dlrate < 1024.0)
    units = 0;
  else if (dlrate < 1024.0 * 1024.0)
    units = 1, dlrate /= 1024.0;
  else if (dlrate < 1024.0 * 1024.0 * 1024.0)
    units = 2, dlrate /= (1024.0 * 1024.0);
  else
    /* Maybe someone will need this, one day. */
    units = 3, dlrate /= (1024.0 * 1024.0 * 1024.0);

  sprintf (res, pad ? "%7.2f %s" : "%.2f %s", dlrate, rate_names[units]);

  return res;
}
ClientReturn freeFile_Client(Client *client, const char *file)
{
  char *message = NULL;
  int cRet;
  if(findElement_List(&(client->lFileUse), (Element)file) == LIST_SUCCESS)
    deleteCurrent_List(&(client->lFileUse));
  
  message = (char *) malloc ((strlen(file) + 7) * sizeof(char));
  if(message == NULL)
    {
      return CLIENT_MALLOC_ERROR;
    }
  sprintf(message, "FREE %s\n", file);
  cRet = sendCommand_Client(client->socket, message);
  free(message);
  return cRet;
}

ClientReturn quit_Client(Client *client)
{
  ClientReturn cRet = CLIENT_SUCCESS;
  char *message = NULL;
  if(client->socket == -1)
    return CLIENT_SUCCESS;
  if(send_ToolsCS(client->socket, "QUIT\n") != TOOLSCSRETURN_SUCCESS)
    {
      cRet = CLIENT_SEND_ERROR;
    }
  if(receive_ToolsCS(&message, client->socket) != TOOLSCSRETURN_SUCCESS)
    {
      if(message)
	free(message);
      return CLIENT_RECV_ERROR;
    }
  else
    {
      if(strncmp(message, "RCPT QUIT", 9) != 0)
	{
	  free(message);
	  return CLIENT_RECV_ERROR;
	}
    }
  free(message);
  message = NULL;
  if(receive_ToolsCS(&message, client->socket) != TOOLSCSRETURN_SUCCESS)
    {
      if(message)
	free(message);
      return CLIENT_RECV_ERROR;
    }
  else
    {
      if(strncmp(message, "OLEH", 4) == 0)
	{
	  shutdown(client->socket, SHUT_RDWR);
	  close(client->socket);
	}
      else
	{
	  free(message);
	  return CLIENT_ERROR;
	}
    }
  free(message);
  return cRet;
}

List *getLFile_Client(Client *client)
{
  List *l;
  l = &(client->lFile);
  return l;
}

List *getLFileUse_Client(Client *client)
{
  List *l;
  l = &(client->lFileUse);
  return l;
}

ClientReturn isHaveList_Client(Client *client)
{
  if(client->haveLFile == 0)
    return CLIENT_DONT_HAVE;
  return CLIENT_HAVE;
}

char *getMessageReturn_Client(ClientReturn cRet)
{
  char *cReturn = NULL;
  switch(cRet)
    {
    case CLIENT_SUCCESS :
      break;
    case CLIENT_ERROR :
      setStringVar_ToolsString(&cReturn, "Erreur indéterminée...");
      break;
    case CLIENT_SEND_ERROR :
      setStringVar_ToolsString(&cReturn, "Erreur lors de l'envoie...");
      break;
    case CLIENT_SERVER_FAIL :
      setStringVar_ToolsString(&cReturn, "Le serveur ne reconnaît pas la commande.");
      break;
    case CLIENT_RECV_ERROR :
      setStringVar_ToolsString(&cReturn, "Erreur lors de la réception...");
      break;
    case CLIENT_MALLOC_ERROR :
      setStringVar_ToolsString(&cReturn, "Erreur de mémoire (plus de place disponible).");
      break;
    case CLIENT_FREE_FAIL :
      setStringVar_ToolsString(&cReturn, "Le fichier à libérer du serveur est inconnue.");
      break;
    case CLIENT_FILE_BUSY :
      setStringVar_ToolsString(&cReturn, "Le fichier est occupé par un autre client.");
      break;
    case CLIENT_FILE_UNKNOW :
      setStringVar_ToolsString(&cReturn, "Le fichier n'est pas connue par le serveur.");
    default :
      break;
    }
  return cReturn;
}
